﻿using System;
using System.IO;
using System.Net;
using AgNet.Models;
using Newtonsoft.Json;
using RestSharp;

namespace AgNet
{
    public partial class AgNetClient
    {
        /// <summary>
        /// Retrieve a AgSession object using the user's Audiogalaxy credentials (email and password). 
        /// Note that you MUST NOT store the user's email or password. 
        /// The AgSession can be stored and remains valid until the user revokes authorization for your application
        /// </summary>
        /// <param name="mail">the user's email address associated with their Audiogalaxy account</param>
        /// <param name="password">the user's password associated with their Audiogalaxy account</param>
        public void LoginAsync(string mail, string password, Action<AgSession> callback)
        {
            var request = this.requestHelper.CreateLoginRequest(this.clientId, this.appSecret, mail, password);

            this.restClient.ExecuteAsync<AgSession>(
                request,
                (restResponse) =>
                {
                    this.CheckResponse(restResponse);
                    callback(restResponse.Data);
                });
        }

        /// <summary>
        /// When your session expires, the API endpoints will respond with HTTP 401
        /// Unauthorized, per the requirement of the OAuth 2.0 spec. 
        /// At any time, you can use the token endpoint with your refresh token 
        /// to get a new session token
        /// </summary>
        /// <param name="previousSession">The session containing the refresh token</param>
        public void RefreshSessionAsync(AgSession previousSession, Action<AgSession> callback)
        {
            var request = this.requestHelper.CreateLoginRequest(this.clientId, this.appSecret, previousSession.RefreshToken);

            this.restClient.ExecuteAsync<AgSession>(
                request,
                (restResponse) =>
                {
                    this.CheckResponse(restResponse);
                    callback(restResponse.Data);
                });
        }

        public void FetchUserInfoAsync(AgSession session, Action<RestResponse<AgUserInfo>> callback)
        {
            var request = this.requestHelper.CreateUserInfoRequest(this.clientId, session.AccessToken);

            this.restClient.ExecuteAsync<AgUserInfo>(
                request,
                (restResponse) =>
                {
                    this.CheckResponse(restResponse);
                    callback(restResponse);
                });
        }

        /// <summary>
        /// Fetch the list of users's playlists, artists, albums or genres.
        /// </summary>
        /// <param name="type">The type of resource (playlists|artists|genres).</param>
        /// <param name="offset">0-based offset to begin retrieving from.</param>
        /// <param name="imgSize">Small, medium or large size image.</param>
        /// <param name="session">The user session.</param>
        /// <param name="callback">The action to perform on completition.</param>
        /// <returns>The list of resources of requested type.</returns>
        public void FetchResourceListAsync(ResourceTypes type, int offset, ImageSizes imgSize, AgSession session, Action<AgResourcesList> callback)
        {
            var request = this.requestHelper.FetchResourceListRequest(type, offset, imgSize, this.clientId, session.AccessToken);

            this.restClient.ExecuteAsync<AgResourcesList>(
                request,
                    (restResponse) =>
                    {
                        this.CheckResponse(restResponse);
                        callback(restResponse.Data);
                    });
        }

        /// <summary>
        /// Fetch the list of songs by playlist, artist, genre.
        /// </summary>
        /// <remarks>NOTE: Album is handled slightly differently</remarks>
        /// <param name="type">The type of resource to look into (playlist|artist|genre).</param>
        /// <param name="resourceId">The id of the resource you wish to fetch.</param>
        /// <param name="offset">0-based offset to begin retrieving from .</param>
        /// <returns></returns>
        public void FetchSongListAsync(ResourceTypes type, string resourceId, int offset, AgSession session, Action<AgSongsList> callback)
        {
            var request = this.requestHelper.FetchSongsListRequest(type, resourceId, offset, this.clientId, session.AccessToken);

            this.restClient.ExecuteAsync<AgSongsList>(
                request,
                (restResponse) =>
                {
                    this.CheckResponse(restResponse);
                    callback(restResponse.Data);
                });
        }

        public void FetchAudioAsync(string audioToken, AgSession session, Action<Stream> callback)
        {
            var request = this.requestHelper.FetchAudio(audioToken, "ogg", "low", "live_stream", this.clientId, session.AccessToken);
            Uri uri = this.restClient.BuildUri(request);
            HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(uri);
#if SILVERLIGHT
            httpWebRequest.AllowReadStreamBuffering = false;
#endif
            RequestState requestState = new RequestState(httpWebRequest, callback);
            httpWebRequest.BeginGetResponse(new AsyncCallback(this.RespCallback), requestState);
        }

        private void RespCallback(IAsyncResult asynchronousResult)
        {
            RequestState requestState = (RequestState)asynchronousResult.AsyncState;
            HttpWebRequest httpWebRequest = requestState.Request;
            WebResponse response = (HttpWebResponse)httpWebRequest.EndGetResponse(asynchronousResult);

            Stream audioStream = response.GetResponseStream();
            requestState.Callback(audioStream);
        }

        private void CheckResponse<T>(RestResponse<T> response)
        {
            if (response.ResponseStatus != ResponseStatus.Completed)
            {
                // TODO: throw a communication error
            }

            var error = JsonConvert.DeserializeObject<Models.AgError>(response.Content);
            if (error.Message != null)
            {
                throw new AgNet.Exceptions.AgException(error.Message, error.Error);
            }
        }

        private class RequestState
        {
            public RequestState(HttpWebRequest request, Action<Stream> callback)
            {
                this.Request = request;
                this.Callback = callback;
            }

            // This class stores the State of the request.
            public HttpWebRequest Request
            {
                get;
                private set;
            }

            public Action<Stream> Callback
            {
                get;
                private set;
            }
        }
    }
}
